package org.acghub.mtdb.common;

import org.acghub.mtdb.util.StringUtil;
import org.acghub.mtdb.util.XMLUtil;
import org.w3c.dom.Element;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * @author zhouhe
 */
public class Naming implements INaming<Naming>, Compilable, INamingSearcher, Dumpable {
    private boolean resolved;
    private final String name;
    private final String[] fullName;
    private final Naming parent;
    private final Set<Naming> children = new LinkedHashSet<>();
    private final Root root;

    protected Naming() {
        this.parent = null;
        root = (Root) this;
        this.name = "";
        this.fullName = new String[]{Integer.toHexString(root.incr()), ""};
    }

    public Naming(Naming parent, String name) {
        this.parent = parent;
        this.root = parent.root;
        this.name = name;
        List<String> list = new ArrayList<>();
        for (Naming p = parent; p != null; p = p.parent) {
            list.add(0, p.name);
        }
        list.add(0, Integer.toHexString(root.incr()));
        list.add(name);
        this.fullName = list.toArray(new String[0]);
        parent.children.stream().filter(i -> !name.isEmpty() && name.equals(i.name)).findAny().ifPresent(n -> {
            throw new RuntimeException("Duplicate name " + this);
        });
        parent.getChildren().add(this);
        root.addSymbol(this.fullName, this);
    }

    Naming(Naming parent, Element self, String key) throws Exception {
        this(parent, self.getAttribute(key).trim());
        for (Element e : XMLUtil.getChildElements(self)) {
            String clazzName = Naming.class.getPackageName() + "." + StringUtil.upperFirstChar(e.getNodeName());
            Class.forName(clazzName).getConstructor(getClass(), Element.class).newInstance(this, e);
        }
    }

    public Naming(Naming parent, Element self) throws Exception {
        this(parent, self, "name");
        this.initialize(self);
    }


    @Override
    public boolean isResolved() {
        return resolved;
    }

    @Override
    public void setResolved(boolean resolved) {
        this.resolved = resolved;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public Root getRoot() {
        return root;
    }

    @Override
    public Naming getParent() {
        return parent;
    }

    @Override
    public Set<Naming> getChildren() {
        return children;
    }

    @Override
    public <T extends Naming> T search(String[] ref, Class<T> clazz) {
        return root.search(ref, clazz);
    }

    @Override
    public void dump(PrintStream ps) {
        if (parent == null) {
            for (Naming child : children) {
                child.dump(ps, "");
            }
        } else {
            dump(ps, "");
        }
    }

    void dump(PrintStream ps, String tab) {
        ps.println(tab + "[" + getClass().getSimpleName() + "]" + name + (!isResolved() ? "[-]" : ""));
        for (Naming child : children) {
            child.dump(ps, tab + "\t");
        }
    }

    @Override
    public boolean resolve() {
        return getChildren() == null || getChildren().isEmpty() || getChildren().stream().allMatch(Compilable::isResolved);
    }

    /**
     * 从xml节点中初始化
     *
     * @param self
     * @throws Exception
     */
    protected void initialize(Element self) throws Exception {
        throw new UnsupportedOperationException("class: " + this.getClass().getName() + " not support initialize");
    }

    @Override
    public String toString() {
        if (parent == null) {
            return "";
        }
        List<Naming> list = new ArrayList<>();
        for (Naming p = parent; p.parent != null; p = p.parent) {
            list.add(0, p);
        }
        list.add(this);
        StringBuilder sb = new StringBuilder();
        String tab = "\t";
        for (Naming n : list) {
            sb.append("[").append(n.getClass().getSimpleName()).append("]").append(n.getName()).append("\n").append(tab);
            tab += "\t";
        }
        return sb.toString();
    }


}
